<template>
  <div class="upload-container">
    <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
      <el-tab-pane label="上传" name="first">
        <div class="upload-button">
          <div class="upload-icon">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" ><path fill="currentColor" d="M160 832h704a32 32 0 1 1 0 64H160a32 32 0 1 1 0-64m384-578.304V704h-64V247.296L237.248 490.048 192 444.8 508.8 128l316.8 316.8-45.312 45.248z"></path></svg>
          </div>
          <div style="width: 100%;text-align: center; color: #0d84ff">
            上传文件
          </div>
          <input
              title=""
              class="input"
              type="file"
              :multiple="props.multiple"
              accept=".*"
              @change="handleUploadChange($event)"
          />
        </div>
        <list-item
            :model-value="fileList"
            :isInUpdate="isInUpdate"
            :multiple="props.multiple"
            @on-remove="handleRemoveFile"
            @pause-upload="pauseUpload"
            @resume-upload="resumeUpload"
            @cancel-single="cancelSingle"
        />
      </el-tab-pane>
      <el-tab-pane label="下载" name="second">
        <DownList/>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script setup>
import 'element-plus/theme-chalk/index.css';
import {computed, ref} from "vue";
import ListItem from "./listItem.vue";
import {ElMessage, ElTabs, ElTabPane} from "element-plus";
import {checkFile, mergeChunk, uploadFile} from "@/api/index.js";
import DownList from "./downList.vue";
const props = defineProps({
  modelValue: {
    type: [Object, Array],
    default: null
  },
  isDrag: Boolean,
  multiple: Boolean,
  isTip: Boolean,
  tip: {
    type: String,
    default: 'jpg/png files with a size less than 500kb',
  },
  chunkSize: {
    type: Number,
    default: 1
  }
})
const emit = defineEmits(['update:modelValue', 'onRemove'])

const currentModelValue = computed({
  get: function () {
    if (props.multiple) fileList.value = props.modelValue
    else fileList.value = [props.modelValue]
    return props.modelValue
  },
  set(value) {
    console.log('------currentModelValue--------',value)
    fileList.value = value
    emit('update:modelValue', value)
  }
})
const currentChunkSize = computed(() => {return props.chunkSize * 1024 * 1024})

const fileList = ref([])
const isInUpdate = ref(0)

const activeName = ref('first')

const handleClick = (tab, event) => {
  console.log(tab, event)
}


const handleUploadChange = (e) => {
  console.log(e.target.files)
  const fileEle = e.target
  // 如果没有文件内容
  if (!fileEle || !fileEle.files || fileEle.files.length === 0) {
    return false
  }
  const files = fileEle.files
  Array.from(files).map(async(item, index) => {
    const file = item
    let taskFileItem  = {
      id: new Date() + index,
      state: 0, // 0是什么都不做,1文件处理中,2是上传中,3是暂停,4是上传完成,5上传中断，6是上传失败
      lastModified: item.lastModified,
      lastModifiedDate: item.lastModifiedDate,
      name: item.name,
      size: item.size,
      type: item.type,
      fileHash: '',
      fileName: item.name,
      fileSize: item.size,
      allChunkList: [],
      whileRequests: [],
      finishNumber: 0,
      errNumber: 0,
      percentage: 0,
      cancel: null,
    }
    let isFind;
    if (fileList.value.length) {
      isFind =  fileList.value.find(fItem => fItem.name === taskFileItem.name && fItem.size === taskFileItem.size)
    }
    if (isFind) {
      ElMessage.warning('文件已存在')
      isFind.state = 4
    } else {
      fileList.value.push(taskFileItem)
      const { fileHash, fileChunkList } = await useWorker(file, currentChunkSize.value)
      console.log(fileHash, '文件hash计算完成')
      // 解析完成开始上传文件
      let baseName = ''
      // 查找'.'在fileName中最后出现的位置
      const lastIndex = item.name.lastIndexOf('.')
      // 如果'.'不存在，则返回整个文件名
      if (lastIndex === -1) {
        baseName = item.name
      }
      // 否则，返回从fileName开始到'.'前一个字符的子串作为文件名（不包含'.'）
      baseName = item.name.slice(0, lastIndex)

      // 这里要注意！可能同一个文件，是复制出来的，出现文件名不同但是内容相同，导致获取到的hash值也是相同的
      // 所以文件hash要特殊处理
      taskFileItem.fileHash = `${fileHash}${baseName}`
      taskFileItem.state = 2
      console.log(fileList.value, 'fileList.value')
      console.log(fileList.value)
      try {
        // // 上传之前要检查服务器是否存在该文件
        const params = {
          fileHash: `${fileHash}${baseName}`,
          fileName: file.name,
        }
        const resp = await checkFile(params);
        console.log('检查文件',resp)
        if (resp.code === 0) {
          const { shouldUpload, uploadedList } = resp.data
          if (!shouldUpload) {
            finishTask(taskFileItem)
            isInUpdate.value++;
            console.log('文件已存在，实现秒传')
            return false
          }
          taskFileItem.allChunkList = fileChunkList.map((item, index) => {
            return {
              fileHash: `${fileHash}${baseName}`,// 总文件hash
              fileSize: file.size,// 总文件size
              fileName: file.name,// 总文件name
              index: index,
              chunkFile: item.chunkFile,// 切片文件本身
              chunkHash: `${fileHash}-${index}`,// 单个切片hash,以 - 连接
              chunkSize: currentChunkSize.value,// 切片文件大小
              chunkNumber: fileChunkList.length,// 切片个数
              finished: false,// 切片是否已经完成
            }
          })
          // 如果已存在部分文件切片，则要过滤调已经上传的切片
          if (uploadedList.length > 0) {
            // 过滤掉已经上传过的切片
            taskFileItem.allChunkList = taskFileItem.allChunkList.filter(
                (item) => !uploadedList.includes(item.chunkHash)
            )

            // 如果存在需要上传的，但是又为空，可能是因为还没合并，
            if (!taskFileItem.allChunkList.length) {
              console.log('执行---合并')
              await handleMerge(taskFileItem)
              return false
            } else {
              // 同时要注意处理切片数量
              taskFileItem.allChunkList = taskFileItem.allChunkList.map(
                  (item) => {
                    return {
                      ...item,
                      chunkNumber: taskFileItem.allChunkList.length,
                    }
                  }
              )
            }
          }
        }
        await uploadTaskFile(taskFileItem, index)
      } catch (err) {}
    }
  })
  e.target.value = null
}
const handleRemoveFile = (row) => {
  console.log('----------handleRemoveFile', row)
  if (!props.multiple) emit('update:modelValue', null)
  else {
    fileList.value = fileList.value.filter(item => item.id !== row.id)
    console.log('-----handleRemoveFile------------',fileList.value)
    emit('update:modelValue', fileList.value)
  }
  emit('onRemove', row)
}
const maxRequest = ref(6)

// 生成文件 hash（web-worker）
const useWorker = (file, chunkSize) => {
  return new Promise((resolve) => {
    const worker = new Worker(
        new URL('./worker/hash-worker.js', import.meta.url)
        // {
        //   type: 'module',
        // }
    )
    worker.postMessage({ file, chunkSize: chunkSize })
    worker.onmessage = (e) => {
      const { fileHash, fileChunkList } = e.data
      if (fileHash) {
        resolve({
          fileHash,
          fileChunkList,
        })
      }
    }
  })
}

// 调取合并接口处理所有切片
const handleMerge = async (taskArrItem) => {
  const { fileName, fileHash } = taskArrItem
  const res = await mergeChunk({
    chunkSize: currentChunkSize.value,
    fileName,
    fileHash,
  }).catch(() => {})
  //  如果合并成功则标识该文件已经上传完成

  if (res && res.code === 0) {
    // 设置文件上传状态
    finishTask(taskArrItem)
    console.log('文件合并成功！')
  } else {
    // 否则暂停上传该文件
    pauseUpload(taskArrItem, true)
    console.log('文件合并失败！')
  }
  // 最后赋值文件切片上传完成个数为0
  taskArrItem.finishNumber = 0
}
// 设置单个文件上传已完成
const finishTask = (item) => {
  item.percentage = 100
  // 4是上传完成
  item.state = 4
}
// 暂停上传（是暂停剩下未上传的）
const pauseUpload = (taskArrItem, elsePause = true) => {
  // elsePause为true就是主动暂停，为false就是请求中断
  // 4是成功 6是失败  如果不是成功或者失败状态，
  if (![4, 6].includes(taskArrItem.state)) {
    // 3是暂停，5是中断
    if (elsePause) {
      taskArrItem.state = 3
    } else {
      taskArrItem.state = 5
    }
  }
  taskArrItem.errNumber = 0

  // 取消还在请求中的所有接口
  if (taskArrItem.whileRequests.length > 0) {
    for (const itemB of taskArrItem.whileRequests) {
      itemB.cancel ? itemB.cancel() : ''
    }
  }
  if (timer.value) clearInterval(timer.value)
  // // 所有剩下的请求都触发取消请求
  // for (const itemB of item.allChunkList) {
  //   //  如果cancel是函数则触发取消函数
  //   itemB.cancel ? itemB.cancel() : ''
  // }
}

// 继续上传
const resumeUpload = async (taskArrItem) => {
  // 2为上传中
  taskArrItem.state = 2
  // 把刚才暂停的正在上传中所有切片放到待上传切片列表中
  taskArrItem.allChunkList.push(...taskArrItem.whileRequests)
  taskArrItem.whileRequests = []
  await uploadTaskFile(taskArrItem)
}
// 取消单个
const cancelSingle = async (taskArrItem) => {
  pauseUpload(taskArrItem)
  // 取消上传后列表删除该文件
  fileList.value = fileList.value.filter(item => item.fileHash !== taskArrItem.fileHash)
}

const timer = ref()
const uploadTaskFile = async (taskArrItem) => {
  // 如果没有需要上传的切片 / 正在上传的切片还没传完，就不做处理
  if (
      taskArrItem.allChunkList.length === 0 ||
      taskArrItem.whileRequests.length > 0
  ) {
    return false
  }
  // 找到文件处于处理中/上传中的 文件列表（是文件而不是切片）
  const isTaskArrIng = fileList.value.filter(
      (itemB) => itemB.state === 1 || itemB.state === 2
  )

  // 实时动态获取并发请求数,每次调请求前都获取一次最大并发数
  // 浏览器同域名同一时间请求的最大并发数限制为6
  // 例如如果有3个文件同时上传/处理中，则每个文件切片接口最多调 6 / 3 == 2个相同的接口
  maxRequest.value = Math.ceil(6 / isTaskArrIng.length)

  // 从数组的末尾开始提取 maxRequest 个元素。
  let whileRequest = taskArrItem.allChunkList.slice(-maxRequest.value)

  // 设置正在请求中的个数
  taskArrItem.whileRequests.push(...whileRequest)
  //  如果总请求数大于并发数
  if (taskArrItem.allChunkList.length > maxRequest.value) {
    // 则减去并发数
    taskArrItem.allChunkList.splice(-maxRequest.value)
  } else {
    // 否则总请求数置空,说明已经把没请求的全部放进请求列表了，不需要做过多请求
    taskArrItem.allChunkList = []
  }

  // 单个分片请求
  const uploadChunk = async (needObj) => {
    const fd = new FormData()
    const {
      fileHash,
      fileSize,
      fileName,
      index,
      chunkFile,
      chunkHash,
      chunkSize,
      chunkNumber,
    } = needObj

    fd.append('fileHash', fileHash)
    fd.append('fileSize', String(fileSize))
    fd.append('fileName', fileName)
    fd.append('index', String(index))
    fd.append('chunkFile', chunkFile)
    fd.append('chunkHash', chunkHash)
    fd.append('chunkSize', String(chunkSize))
    fd.append('chunkNumber', String(chunkNumber))
    const res = await uploadFile(fd, (onCancelFunc) => {
      // 在调用接口的同时，相当于同时调用了传入的这个函数，又能同时拿到返回的取消方法去赋值
      needObj.cancel = onCancelFunc
    }).catch(() => {})

    console.log('上传切片', res)
    // 先判断是不是处于暂停还是取消状态
    // 你的状态都已经变成暂停或者中断了,就什么都不要再做了,及时停止
    if (taskArrItem.state === 3 || taskArrItem.state === 5) {
      return false
    }

    // 请求异常,或者请求成功服务端返回报错都按单片上传失败逻辑处理,.then.catch的.catch是只能捕捉请求异常的
    if (!res || res.code === -1) {
      taskArrItem.errNumber++
      // 超过3次之后直接上传中断
      if (taskArrItem.errNumber > 3) {
        console.log('切片上传失败超过三次了')
        pauseUpload(taskArrItem, false) // 上传中断
      } else {
        console.log('切片上传失败还没超过3次')
        await uploadChunk(needObj) // 失败了一片,继续当前分片请求
      }
    } else if (res.size) {
      // 单个文件上传失败次数大于0则要减少一个
      taskArrItem.errNumber > 0 ? taskArrItem.errNumber-- : 0
      // 单个文件切片上传成功数+1
      taskArrItem.finishNumber++
      // 单个切片上传完成
      needObj.finish = true
      taskArrItem = singleFileProgress(needObj, taskArrItem) // 更新进度条
      // 上传成功了就删掉请求中数组中的那一片请求
      taskArrItem.whileRequests = taskArrItem.whileRequests.filter(
          (item) => item.chunkFile !== needObj.chunkFile
      )

      // 如果单个文件最终成功数等于切片个数
      if (taskArrItem.finishNumber === chunkNumber) {
        // 全部上传完切片后就开始合并切片
        await handleMerge(taskArrItem)
        ElMessage.success('上传完成')
      } else {
        // 如果还没完全上传完，则继续上传
        await uploadTaskFile(taskArrItem)
      }
    }
  }

  // 开始单个上传
  for (const item of whileRequest) {
    await uploadChunk(item)
  }
/*  timer.value = setInterval(() => {
    nextTick(() => {
      console.log('开始上传', taskArrItem.allChunkList.length)
      taskArrItem.allChunkList[0].finished = true
      taskArrItem.finishNumber++;
      taskArrItem = singleFileProgress(taskArrItem.allChunkList[0], taskArrItem)
      isInUpdate.value++;
      if (taskArrItem.allChunkList.length === 0) {
        clearInterval(timer.value)
      }
    })
  },20)*/
}
// 更新单个文件进度条
const singleFileProgress = (needObj, taskArrItem) => {
  console.log('更新单个文件进度条')
  // 即使是超时请求也是会频繁的返回上传进度的,所以只能写成完成一片就添加它所占百分之多少,否则会造成误会
  taskArrItem.percentage = Number(
      ((taskArrItem.finishNumber / needObj.chunkNumber) * 100).toFixed(2)
  )
  taskArrItem.allChunkList = taskArrItem.allChunkList.filter(item => !item.finished)

  isInUpdate.value++;
  return taskArrItem
}

</script>
<style scoped>
.upload-container {
  height: 100%;
}
.upload-button {
  border-radius: 4px;
  border: 1px dashed rgba(13,132,255, 1);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  padding: 100px 0;
  cursor: pointer;
  background-color: rgba(13,132,255, 0.1);;
}
.upload-icon svg {
    width: 36px;
    color: #0d84ff;
}
.input {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  opacity: 0;
  cursor: pointer;
}
</style>
